package net.protocol.diameter.dictionary;

import static net.protocol.diameter.dictionary.AVPRule.Level.*;

/**
 * <pre>
 * <p><b>Diameter AVPs Defined:</b>                                           
 *                                             +---------------------+
 *                                             |    AVP Flag rules   |
 *                                             |----+-----+----+-----|----+
 *                         AVP                 |    |     |SHLD| MUST|    |
 *     Attribute Name      Code   Data Type    |MUST| MAY | NOT|  NOT|Encr|
 *     ----------------------------------------|----+-----+----+-----|----|
 *     Proxy-State         33     OctetString  | M  |     |    | P,V | N  |
 *
 * <p><b>Sample usages:</b>
 *
 *     avpFormat avpFormat = new avpFormat(33, "Proxy-State", 0, DataFormat.OctetString,
 *          new AVPFlagRule(RLevel.MUST, RLevel.MUSTNOT, RLevel.MUSTNOT, false),
 *          Standard.Base.name());
 *     |    
 *     avpFormat avpFormat = new avpFormat.Builder(33, DataFormat.OctetString)
 * 				.name("Proxy-State")
 * 				// .vendorId(0)
 * 				.rule(new AVPFlagRule(RLevel.MUST, RLevel.MUSTNOT, RLevel.MUSTNOT, false))
 * 				// .standard(Standard.Base.name())
 * 				.build();
 *
 *     return new AVPRule(avpFormat, AVPRule.Level.Required, 1, 1);
 * </pre>
 *
 * @author jinhongw@gmail.com
 */
public class AVPRule {
	public final static int DEFAULT_MIN = 0;
	public final static int DEFAULT_MAX = -1;
	public final static int DEFAULT_POSITION = -1;

	private final AVPFormat avpFormat;

	private final AVPRule.Level level;
	private final int min;
	private final int max;
	private final int position;
    
	/**
	 * @param AVPFormat
	 *            AVP Format Specification
	 * @param level
	 *            an enum [Fixed, Required, Optional]
	 * @param min
	 *            the minimum number of times the element[<code>AVP</code>] may be
	 *            present,the default value is zero.
	 * @param max
	 *            the maximum number of times the element[<code>AVP</code>] may be present,
	 *            the default value is infinity, a value of zero implies the AVP
	 *            MUST NOT be present.
	 */
    public AVPRule(AVPFormat AVPFormat, AVPRule.Level level, int min, int max) {
		this(AVPFormat, level, min, max, DEFAULT_POSITION);
	}

	/**
	 * @param avpFormat
	 *            AVP Format Specification
	 * @param level
	 *            an enum [Fixed, Required, Optional]
	 * @param min
	 *            the minimum number of times the element[<code>AVP</code>] may be
	 *            present,the default value is zero.
	 * @param max
	 *            the maximum number of times the element[<code>AVP</code>] may be present,
	 *            the default value is infinity, a value of zero implies the AVP
	 *            MUST NOT be present.
	 * @param position
	 *            defines the fixed position of an AVP, the default value means
	 *            can appear anywhere in the message.
	 * 
	 * throws IllegalArgumentException
	 */
    public AVPRule(AVPFormat avpFormat, AVPRule.Level level, int min, int max,
                   int position) {
		if (avpFormat == null)
			throw new IllegalArgumentException("AVPFormat is null");
		
		this.avpFormat = avpFormat;

		if ((level == Fixed || level == Required) && min < 1)
			throw new IllegalArgumentException(
					"Fixed and Required AVP present min time must > 0, but was:<" + min + ">");
		
		if (level == Fixed && position < 0)
			throw new IllegalArgumentException(
					"Fixed AVP position must >= 0, but was:<" + position + ">");
		
		if (level == Optional && min > 0)
			throw new IllegalArgumentException(
					"Optional AVP present min time must <= 0, but was:<" + min + ">");

		this.level = level;
		this.min = min;
		this.max = max;
		this.position = position;
	}

	/**
	 * Returns AVP Format Specification in a <code>DiameterFormat</code>.
	 * 
	 * @return AVP Format Specification
	 */
	public AVPFormat getAVPFormat() {
		return avpFormat;
	}

	/**
	 * Returns an enum indicating level of required in a <code>DiameterFormat</code>.
	 * 
	 * @return an enum [Fixed, Required, Optional]
	 */
	public AVPRule.Level getLevel() {
		return level;
	}
	
	public QualQty getQualQty() {
		if (min == 1 && max == 1)
			return QualQty.One;
		
		if (min == 0 && max == 1)
			return QualQty.Zero_One;
		
		if (min == 1 && max != 0)
			return QualQty.One_Many;
		
		return QualQty.Any;
	}

	/**
	 * Returns the minimum number of times AVP may be present in a <code>DiameterFormat</code>.
	 * 
	 * @return the minimum number of times the element[<code>AVP</code>] may be present,the
	 *         default value is zero.
	 */
	public int getMin() {
		return min;
	}

	/**
	 * Returns the maximum number of times AVP may be present in a <code>DiameterFormat</code>.
	 * 
	 * @return the maximum number of times the element[<code>AVP</code>] may be present, the
	 *         default value is infinity, a value of zero implies the AVP MUST
	 *         NOT be present.the default value is -1.
	 */
	public int getMax() {
		return max;
	}

	/**
	 * Returns fixed position of an avp in a <code>DiameterFormat</code>.
	 * 
	 * @return the fixed position of an <code>AVP</code>, the default value means can appear
	 *         anywhere in the message.
	 */
	public int getPosition() {
		return position;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		AVPRule other = (AVPRule) obj;
		if (avpFormat == null) {
			if (other.avpFormat != null)
				return false;
		} else if (!avpFormat.equals(other.avpFormat))
			return false;
		if (max != other.max)
			return false;
		if (min != other.min)
			return false;
		if (position != other.position)
			return false;
		if (level != other.level)
			return false;
		return true;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((avpFormat == null) ? 0 : avpFormat.hashCode());
		result = prime * result + max;
		result = prime * result + min;
		result = prime * result + position;
		result = prime * result + ((level == null) ? 0 : level.hashCode());
		return result;
	}
	
	@Override
	public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(getClass().getSimpleName()).append(" [");
        buf.append(avpFormat).append(", ");
        buf.append("name=").append(level).append(", ");
        buf.append("min=").append(min).append(", ");
        buf.append("max=").append(max).append(", ");
        buf.append("position=").append(position);
        buf.append("]");
        return buf.toString();
	}

	/**
	 * A AVPRule.Level. A AvpRule can be in one of the following Level:
	 * <ul>
	 * <li>{@link #Fixed}<br>
	 * [qual] "<" avp-spec ">"; Defines the fixed position of an AVP.</li>
	 * <li>{@link #Required}<br>
	 * [qual] "{" avp-spec "}"; The AVP MUST be present and can appear anywhere
	 * in the message.</li>
	 * <li>{@link #Optional}<br>
	 * [qual] "[" avp-name "]"; The avp-name in the 'optional' rule cannot
	 * evaluate to any AVP Name which is included in a fixed or required rule.
	 * The AVP can appear anywhere in the message.</li>
	 * </ul>
	 */
	public static enum Level {
		Fixed, Required, Optional;
	}

	public static enum QualQty {
		/**
		 * 0*1, it means zero or only one - only for Optional AVP
		 */
		Zero_One,
		
		/**
		 * 0* | *, it means zero or more - only for Optional AVP
		 */
		Any,

		/**
		 * 1*1, it means must only one - only for Non-Optional AVP
		 */
		One,

		/**
		 * 1*, it means least one more - only for Non-Optional AVP
		 */
		One_Many
	}
}
